# 
# Copyright (C) 2013 hakan.johansson@gmail.com
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import os, sha, json, exif, urllib, time, cgi
from PIL import Image
from random import randint
from log import *
from passlib.apps import custom_app_context as pwd_context

from config import *
from CookieHandler import *

class ServerSide(object):
    def __init__(self, environ):
        self.album = 0
        self.contentType = "text/html"
        self.environ = environ
        self.form = cgi.FieldStorage(fp=environ['wsgi.input'], environ=environ)
        self.cookie = ""
        self.sessionId = -1
        self.status = "200 OK"

        Log.printToFile("*"*50)
        for key in self.form.keys():
            Log.printToFile(key+":"+self.form[key].value)
        Log.printToFile("*"*50)

        #This dictionary creates a connection between the clientside request and the serverside processing
        self.cmdDict ={ "getNumOfImages":self.getNumOfImages,\
                        "getSubAlbums":self.getSubAlbums,\
                        "getAlbumThumb":self.getAlbumThumb,\
                        "getAlbumImage":self.getAlbumImage,\
                        "getImagePreview":self.getImagePreview,\
                        "getPreviewImages":self.getPreviewImages,\
                        "getImageInfo":self.getImageInfo,\
                        "getFullscreenImage":self.getFullscreenImage,\
                        "login":self.login,\
                        "logout": self.logout,\
                        "getLoggedInStatus":self.getLoggedInStatus,\
                        "saveAlbumDescription":self.saveAlbumDescription,\
                        "saveImageDescription":self.saveImageDescription}


    def saveImageDescription(self):
        if not self.sessionId:
            Log.printToFile("ServerSide::saveImageDescription() not logged in!")
            self.status = "500 Error: not logged in!"
            return "Error: not logged in!"

        if(not self.form.has_key("text") or not self.form.has_key("index")):
            Log.printToFile("ServerSide::saveImageDescription() missing data!")
            self.status = "500 Error: data missing!"
            return "Error: data missing!"

        Log.printToFile("ServerSide::saveImageDescription() text: %s index: %s"%(self.form["text"].value, self.form["index"].value))

        error = self.album.saveImageDescription(self.form["text"].value, self.form["index"].value)
        if error:
            Log.printToFile("ServerSide::saveDescription() could not save description error: %s"%error)
            self.status = "500 Error: Could not save description!"
            return "Error: Could not save description!"

        return "Description saved."

    def saveAlbumDescription(self):
        if not self.sessionId:
            Log.printToFile("ServerSide::saveAlbumDescription() not logged in!")
            self.status = "500 Error: not logged in!"
            return "Error: not logged in!"

        if(not self.form.has_key("text")):
            Log.printToFile("ServerSide::saveAlbumDescription() missing data!")
            self.status = "500 Error: data missing!"
            return "Error: data missing!"
            
        Log.printToFile("ServerSide::saveAlbumDescription() text: %s"%(self.form["text"].value))

        error = self.album.saveAlbumDescription(self.form["text"].value)
        if error:
            Log.printToFile("ServerSide::saveDescription() could not save description error: %s"%error)
            self.status = "500 Error: Could not save description!"
            return "Error: Could not save description!"

        return "Description saved."

    def getLoggedInStatus(self):
        Log.printToFile("ServerSide::getLoggedInStatus sessionId: %s"%(self.sessionId))
        if self.sessionId > 0:
            return "true"
        else:
            return "false"

    def logout(self):
        Log.printToFile("ServerSide::logout sessionId: %s"%(self.sessionId))
        if self.sessionId > 0:
            self.cookie = CookieHandler().deleteCookie(self.sessionId)
        return "success"

    def login(self):
        Log.printToFile("ServerSide::login()")
        result = "fail"
        try:
            user = self.form["user"].value
            passw = self.form["pass"].value
        except:
            user = ""
            passw= ""

        pfile = open(passwordFile, "r").read()
        username = pfile.split(":")[0]
        encryptedPass = pfile.split(":")[1]
        if user == username and pwd_context.verify(passw, encryptedPass):
            self.cookie = CookieHandler().createCookie()
            result = "success"

        return result

    def getFullscreenImage(self):
        #Log.printToFile("ServerSide::processRequest() getAlbumImage")
        self.contentType = "image/jpeg"
        return self.album.getFullscreenImage(self.form["imageId"].value)

    def getImageInfo(self):
        Log.printToFile("ServerSide::processRequest() getImageInfo")
        imageId = self.form["imageId"].value
        return self.album.getImageInfo(imageId)

    def getPreviewImages(self):
        Log.printToFile("ServerSide::processRequest() getPreviewImages")
        return self.album.getPreviewImagesJson()

    def getNumOfImages(self):
        Log.printToFile("ServerSide::processRequest() getPreviewImages")
        return str(self.album.numberOfImages)

    def getSubAlbums(self):
        Log.printToFile("ServerSide::processRequest() getSubAlbums")
        return self.album.getSubAlbums(self.sessionId)

    def getAlbumThumb(self):
        #Log.printToFile("ServerSide::processRequest() getAlbumThumb")
        self.contentType = "image/jpeg"
        return self.album.getAlbumThumb()

    def getAlbumImage(self):
        #Log.printToFile("ServerSide::processRequest() getAlbumImage")
        self.contentType = "image/jpeg"
        return self.album.getImageData(self.form["imageId"].value)

    def getImagePreview(self):
        #Log.printToFile("ServerSide::processRequest() getAlbumImage")
        self.contentType = "image/jpeg"
        return self.album.getPreviewImage(self.form["imageId"].value)

    def processRequest(self):
        data = "wtf?"
        Log.printToFile("ServerSide::processRequest() form.keys:%s"%(self.form.keys()))

        self.sessionId = CookieHandler().getSessionId(self.environ)
        Log.printToFile("ServerSide::processRequest() sessionId: %s "%(self.sessionId))

        if(self.form.has_key("albumPath")):
            path = urllib.unquote(self.form["albumPath"].value)
            if(not self.validPath(path)):
                path = albumdir
            Log.printToFile("ServerSide::processRequest() valid path: %s "%(path))
        else:
            path = albumdir
        self.album = Album(path)

        for key in self.cmdDict.keys():
            if self.form.has_key(key):
                data = self.cmdDict[key]()

        responseHeaders = []
        responseHeaders.append(('Content-type', self.contentType))
        responseHeaders.append(('Content-Length', str(len(data))))

        if self.cookie:
            responseHeaders.append(('Set-Cookie', self.cookie["session"].OutputString()))

        return self.status, responseHeaders, data


    def validPath(self, path):
        absolute_path = os.path.join(albumdir, path)
        if(not os.path.exists(absolute_path)):
            return False

        #check if the requested album path is within the default album dir
        normalized_path = os.path.normpath(absolute_path)
        # security check to prevent directory traversal
        if not normalized_path.startswith(albumdir):
            Log.printToFile("ServerSide::validPath() Error: Invalid path detected! absolute_path: %s"%(absolute_path))
            return False
        else:
            return True


class Album(object):
    def __init__(self, path):
        Log.printToFile("Album::init() path:%s"%(path))
        self._path = path.replace(albumdir,"")
        self._name = path.split("/")[-1]
        self._images = []
        self._subAlbums = []
        self._description = "No description"
        self._populate()

    def getImageInfo(self, imageId):
        Log.printToFile("Album:getImageInfo imageId:%s"%(imageId))
        name = os.path.basename(self._getPicturePath(imageId))
        try:
            description = open(self._getPicturePath(imageId)+".txt", "r").read().decode('unicode-escape').encode("utf-8")
        except:
            description = ""
        data = json.dumps({"name":name, "description":description})
        return data

    def getPreviewImagesJson(self):
        Log.printToFile("Album:getPreviewImagesJson")
        images = []
        id = 0
        for image in self._images:
            images.append({"name":os.path.basename(image),"imageId":id})
            id += 1
        images.sort()
        data= json.dumps(images)
        Log.printToFile("Album:getPreviewImagesJson data: %s"%(data))
        return data

    def getSubAlbums(self, sessionId):
        Log.printToFile("Album::getSubAlbums()")
        subAlbums = []
        for currentdir in self._subAlbums:
            path = os.path.join(albumdir, self.path, currentdir)
            Log.printToFile("Album::getSubAlbums() %s %s %s"%(albumdir, self.path, currentdir))
            if not os.path.isfile(path) and (currentdir[0] != "_" or sessionId > 0):  # TODO?
                album = Album(path)
                Log.printToFile("Album::getSubAlbums() dir:%s"%(currentdir))
                subAlbums.append({"name":album.name,"description":album.description,"cwd":path.replace(albumdir,""),\
                                              "numberOfImages":album.numberOfImages, "numberOfAlbums":album.numberOfAlbums})
                Log.printToFile("ServerSide::getSubAlbums() name:%s \ndescription %s\n cwd: %s numberOfImages: %s"%(album.name, album.description, path,album.numberOfImages))
        subAlbums.sort()
        data = json.dumps(subAlbums)
        #Log.printToFile("Album::getAlbums() data:%s"%(data))
        return data

    def getAlbumThumb(self):
        path = self._getPicturePath(0)
        Log.printToFile("Album::getAlbumThumb")
        hashname = sha.new(path).hexdigest()
        hashfilePath = albumdir + self._path+"/.thumbs/"+hashname
        if(os.path.isfile(hashfilePath) and not self._cacheNeedsUpdate(path, hashfilePath) and use_cache):
            return open(hashfilePath,"rb").read()
        else:
            if(not os.path.exists(albumdir + self._path+"/.thumbs")):
                os.mkdir(albumdir + self._path+"/.thumbs")
            im = Image.open(path)
            im.thumbnail(thumbsize, Image.ANTIALIAS)
            tags = exif.parse(path, 0, mode=exif.ASCII )
            if tags.has_key("Orientation"):
                if tags["Orientation"] == "8":
                    im = im.rotate(90)
                if tags["Orientation"] == "6":
                    im = im.rotate(270)
            width, height = im.size
            #if width > height:
            #    im = im.transform(thumbsize, Image.EXTENT, (0, 0, width, height), Image.BICUBIC)
            #else:
            #    im = im.transform(thumbsize, Image.EXTENT, (0, 0, height, width), Image.BICUBIC)
            im.save(hashfilePath, "JPEG")
            return im.tostring("jpeg", "RGB", 0, 0)

    def getPreviewImage(self,id):
        Log.printToFile("Album::getPreviewImage id:%s"%(id))
        path =self._getPicturePath(id)
        hashname = sha.new(path).hexdigest()
        hashfilePath = albumdir + self._path+"/.cache/"+hashname
        if(os.path.isfile(hashfilePath) and not self._cacheNeedsUpdate(path, hashfilePath) and use_cache):
            return open(hashfilePath,"rb").read()
        else:
            if(not os.path.exists(albumdir + self._path+"/.cache")):
                os.mkdir(albumdir + self._path+"/.cache")
            im = Image.open(path)
            tags = exif.parse(path, 0, mode=exif.ASCII )
            if tags.has_key("Orientation"):
                if tags["Orientation"] == "8":
                    im = im.rotate(90)
                if tags["Orientation"] == "6":
                    im = im.rotate(270)
            #Log.printToFile("Album::getPreviewImage no cache for path:%s"%(path))
            width, height = im.size
            #Log.printToFile("Album::getPreviewImage %s:%s"%(width, height))
            im.thumbnail(preview, Image.ANTIALIAS)
            im.save(hashfilePath, "JPEG")
            return im.tostring("jpeg", "RGB", 0, 0)
        
    def getImageData(self, id):
        Log.printToFile("Album::getImageData id:%s"%(id))
        path = self._getPicturePath(id)
        hashname = sha.new(path).hexdigest() + "_prev"
        hashfilePath = albumdir + self._path+"/.cache/"+hashname
        if(os.path.isfile(hashfilePath) and not self._cacheNeedsUpdate(path, hashfilePath) and use_cache):
            return open(hashfilePath,"rb").read()
        else:
            if(not os.path.exists(albumdir + self._path+"/.cache")):
                os.mkdir(albumdir + self._path+"/.cache")
            im = Image.open(path)
            tags = exif.parse(path, 0, mode=exif.ASCII )
            if tags.has_key("Orientation"):
                if tags["Orientation"] == "8":
                    im = im.rotate(90)
                if tags["Orientation"] == "6":
                    im = im.rotate(270)

            width, height = im.size
            im.thumbnail(sivpreview, Image.ANTIALIAS)
            im.save(hashfilePath, "JPEG")
            return im.tostring("jpeg", "RGB", 0, 0)

    def getFullscreenImage(self, id):
        Log.printToFile("Album::getFullscreenImage id:%s"%(id))
        path = self._getPicturePath(id)
        im = Image.open(path)
        tags = exif.parse(path, 0, mode=exif.ASCII )
        if tags.has_key("Orientation"):
            if tags["Orientation"] == "8":
                im = im.rotate(90)
            if tags["Orientation"] == "6":
                im = im.rotate(270)
        return im.tostring("jpeg", "RGB", 0, 0)

    def saveImageDescription(self, text, index):
        descFilePath = self._getPicturePath(index) + ".txt"
        Log.printToFile("saveImageDescription() index: %s path: %s"%(index, descFilePath))
        text = text.decode("utf-8").encode('ascii', "xmlcharrefreplace")
        try:
            open(descFilePath, "w").write(text)
        except Exception, e:
            return e

        return 0

    def saveAlbumDescription(self, text):
        descFilePath = os.path.join(albumdir, self._path, "desc.txt")
        text = text.decode("utf-8").encode('ascii', "xmlcharrefreplace")
        try:
            open(descFilePath, "w").write(text)
        except Exception, e:
            return e

        return 0


    def _getPicturePath(self, index):
        index = int(index)
        if(len(self._images) > index):
            Log.printToFile("Album:getPicturePath() totalImages:%s index:%s"%(len(self._images), index))
            return os.path.join(albumdir, self.path, self._images[index])
        else:
            return "res/album_default.png"

    def _getImageCount(self):
        return len(self._images)

    def _getSubAlbumCount(self):
        return len(self._subAlbums)

    def _getDescription(self):
        return self._description

    def _getName(self):
        return self._name

    def _getPath(self):
        return self._path

    def _populate(self):
        Log.printToFile("Album::populate()")
        path = albumdir+self.path
        for file in os.listdir(path):
            completePath = os.path.join(path, file)
            #Log.printToFile("Album::populate() file:%s"%(completePath))
            if os.path.isfile(completePath):
                if file.split(".")[-1].lower() == "jpg": # TODO only jpg?
                    #Log.printToFile("Album::populate() name: %s found image-path: %s"%(self.name, file))
                    self._images.append(file)
                elif file == "desc.txt":
                    #Log.printToFile("Album::populate() name: %s found description-path: %s"%(self.name, file))
                    self._description = open(completePath, "r").read().decode('unicode-escape').encode("utf-8")

            elif file != ".cache" and file != ".thumbs":
                Log.printToFile("Album::populate() found subAlbum: %s"%(file))
                self._subAlbums.append(file)
        self._images.sort()
        #Log.printToFile("\n".join(self._images))

    def _cacheNeedsUpdate(self, path, cachePath):
        Log.printToFile("Album::_cacheNeedsUpdate()")
        cacheDate = time.gmtime(os.path.getmtime(cachePath))
        imageDate = time.gmtime(os.path.getmtime(path))

        if(imageDate > cacheDate):
            os.remove(cachePath)
            return 1
        return 0

    numberOfImages = property(_getImageCount)
    numberOfAlbums = property(_getSubAlbumCount)
    description = property(_getDescription)
    name = property(_getName)
    path = property(_getPath)

